function blit_rotate_scale_to_frame(source_image, origin, pivot_point, width, height, rotation)
    local image_rect = engine.surface.get_rect(source_image, {
        topleft = {
            origin.x - pivot_point.x,
            origin.y - pivot_point.y
        }
    })
    local image_center_rect = engine.rect.get_center(image_rect)
    local offset_center_to_pivot = {origin.x - image_center_rect[1], origin.y - image_center_rect[2]}
    offset_center_to_pivot.x = offset_center_to_pivot[1] * width
    offset_center_to_pivot.y = offset_center_to_pivot[2] * height
    local rotated_offset = engine.math.vector2.rotate(offset_center_to_pivot, -rotation)
    local rotated_image_center = {origin.x - rotated_offset[1], origin.y - rotated_offset[2]}
    local scaled_image = engine.transform.scale(source_image, width, height)
    local rotated_image = engine.transform.rotate(scaled_image, rotation)
    local rect = engine.surface.get_rect(rotated_image, {center = rotated_image_center})
    engine.utilities.blit_frame(engine.current_frame.number, rotated_image, {rect[0], rect[1]})
    return rotated_image
end

function blitAnimObjFromBone(anim_obj, bone)
    engine.animated_object.update(anim_obj, engine.current_frame.number)
    local origin = {
        x = bone.position[1],
        y = bone.position[2]
    }
    local pivot_point = {
        x = anim_obj.pivot_point[0],
        y = anim_obj.pivot_point[1]
    }
    local angle = bone.angle
    final = blit_rotate_scale_to_frame(anim_obj.surface, origin, pivot_point, anim_obj.width, anim_obj.height, -angle)
    return final
end

function calculate_angle(point1, point2, point3)
    -- Calculate vectors
    local vector1 = {point1[1] - point2[1], point1[2] - point2[2]}
    local vector2 = {point3[1] - point2[1], point3[2] - point2[2]}

    -- Calculate dot product
    local dot_product = vector1[1] * vector2[1] + vector1[2] * vector2[2]

    -- Calculate magnitudes
    local magnitude1 = ((vector1[1] ^ 2) + (vector1[2] ^ 2)) ^ 0.5
    local magnitude2 = ((vector2[1] ^ 2) + (vector2[2] ^ 2)) ^ 0.5

    -- Calculate the angle in radians
    local angle_rad = math.acos(dot_product / (magnitude1 * magnitude2))

    -- Convert the angle to degrees
    local angle_deg = math.deg(angle_rad)

    return angle_deg
end

-- Forward Kinematics System
bones = {
    joint_color = {255, 255, 255},
    bone_color = {0, 0, 0},
    bone_width = 2,
    joint_radius = 3
}

function bones.createBone(length, angle, parent)
    local bone = {
        length = length,
        angle = angle,
        parent = parent,
        children = {}
    }
    return bone
end

function bones.addBoneChild(parent, child)
    table.insert(parent.children, child)
end

function bones.updateBones(parentBone, parentPosition)
    local x = parentPosition[1] + parentBone.length * math.cos(math.rad(parentBone.angle))
    local y = parentPosition[2] + parentBone.length * math.sin(math.rad(parentBone.angle))
    parentBone.position = {x, y}

    for _, child in ipairs(parentBone.children) do
        bones.updateBones(child, parentBone.position)
    end
end

function bones.drawBone(bone)
    engine.draw.circle(engine.current_frame.surface, bones.joint_color, {bone.position[1], bone.position[2]}, bones.joint_radius)
    
    if bone.parent then
        engine.draw.line(engine.current_frame.surface, bones.bone_color, {bone.parent.position[1], bone.parent.position[2]}, {bone.position[1], bone.position[2]})
    end
end

-- JSON Utilities
json_utils = {}
json_utils.skeleton = {}
function json_utils.skeleton.load_skeleton(relpath, skeleton)
    local abspath = engine.path.get_absolute(relpath)
    local json_string = fileio.readall(abspath)
    local json_table = json.decode(json_string)
    skeleton['position'] = json_table['position']

    if not skeleton['bones'] then
        skeleton['bones'] = {}
    end
    skeleton['root'] = bones.createBone(0, 0, nil)

    for k, v in pairs(json_table['bones']) do
        skeleton['bones'][k] = v
        skeleton['bones'][k]['bone'] = bones.createBone(v['length'], v['rotation'], skeleton['root'])
    end

    for k, _ in pairs(json_table['bones']) do
        parentBone = skeleton['bones'][k]['parent']
        if parentBone then
            skeleton['bones'][k]['bone'].parent = skeleton['bones'][parentBone]['bone']
        end
        for _, child in ipairs(skeleton['bones'][k]['children']) do
            bones.addBoneChild(skeleton['bones'][k]['bone'], skeleton['bones'][child]['bone'])
        end
    end
end

function json_utils.skeleton.update_skeleton(skeleton, boneName, parentPosition)
    local x = parentPosition[1] + skeleton['bones'][boneName]['bone'].length * math.cos(math.rad(skeleton['bones'][boneName]['bone'].angle))
    local y = parentPosition[2] + skeleton['bones'][boneName]['bone'].length * math.sin(math.rad(skeleton['bones'][boneName]['bone'].angle))
    skeleton['bones'][boneName]['bone'].position = {x, y}

    for _, child in pairs(skeleton['bones'][boneName].children) do
        json_utils.skeleton.update_skeleton(skeleton, child, skeleton['bones'][boneName]['bone'].position)
    end
end

function json_utils.skeleton.update_skeleton_root(skeleton, position)
    skeleton['root'].position = position
    for child, _ in pairs(skeleton['bones']) do
        if skeleton['bones'][child]['parent'] == nil then
            json_utils.skeleton.update_skeleton(skeleton, child, skeleton['root'].position)
        end
    end
end

json_utils.anim_objs = {}
function json_utils.anim_objs.load_objects(relpath, objects)
    local abspath = engine.path.get_absolute(relpath)
    local json_string = fileio.readall(abspath)
    local json_table = json.decode(json_string)

    for k, v in pairs(json_table) do
        anim_obj = engine.animated_object.create(v['position'][1], v['position'][2], v['scale'][1], v['scale'][2], v['rotation'], v['pivot'])
        if v['color'] then -- Setting the color to null will result in leaving the anim obj surface blank
            engine.draw.rectangle(anim_obj.surface, v['color'], {0, 0}, v['scale'])
        end
        objects[k] = anim_obj
    end
end
